package engine.scene.tree;

import java.util.ArrayList;

import engine.gameObject.DynamicObject;
import engine.gameObject.GameObject;
import engine.scene.Frustum;

public class Octree {

	private Node root;
	private double[] min;
	private double minSize;
	
	private ArrayList<DynamicObject> dynamicObjects;
	private ArrayList<GameObject> largeObjects;
	
	public Octree(Frustum frustum, double[] min, double[] max, double minSize) {
		if(min.length != 3 || max.length != 3) {
			throw new IllegalArgumentException("min and max must contain 3 elements");
		}
		
		dynamicObjects = new ArrayList<DynamicObject>();
		largeObjects = new ArrayList<GameObject>();
		
		//save the values in the octree
		this.minSize = minSize;
		
		//compute the sizes of the bounding box of the octree
		double[] size = new double[3];
		for(int i = 0; i < size.length; i++) {
			size[i] = max[i] - min[i];
		}
		
		//find the maximal size length of the octree
		double maxSize = Double.MIN_VALUE;
		for(int i = 0; i < size.length; i++) {
			maxSize = Math.max(maxSize, size[i]);
		}
		
		//find the size length, that the octree can be cubic with a minimal size of minSize
		double totalSize = minSize;
		while(totalSize < maxSize) {
			totalSize *= 2;
		}
		
		//compute new min and max points
		double[] newMin = new double[3];
		double[] newMax = new double[3];
		for(int i = 0; i < newMin.length; i++) {
			double center = min[i] + size[i] / 2;
			newMin[i] = center - totalSize / 2;
			newMax[i] = center + totalSize / 2;
		}
		
		this.min = newMin;
		
		if(Math.abs(totalSize - minSize) < 0.0001) {
			root = new Leaf(frustum, newMin, newMax);
		} else {
			root = new InnerNode(frustum, newMin, newMax);
		}
	}
	
	public void insert(GameObject gameObject) {
		gameObject.setCurrentOctree(this);
		
		if(gameObject.isMarkAsLarge()) {
			largeObjects.add(gameObject);
		} else {
			if(!(gameObject instanceof DynamicObject)) {
				root.insert(gameObject);
			}
			
			if(gameObject instanceof DynamicObject) {
				dynamicObjects.add((DynamicObject)gameObject);
			}
		}
	}
	
	public void render(float timeElapsed) {
		root.render(timeElapsed);
		
		for(GameObject g: largeObjects) {
			g.renderAbs(timeElapsed);
			g.setRendered(false);
		}
		
		for(DynamicObject d: dynamicObjects) {
			d.renderAbs(timeElapsed);
		}
	}
	
	public void reset() {
		root.reset();
	}
	
	public void update(float timeElapsed) {
		for(DynamicObject d: dynamicObjects) {
			d.updateAbs(timeElapsed);
		}
	}
	
	/**
	 * Returns any object in the leaves, that are in contact with the bounding sphere of the game object. 
	 * @param leaves
	 */
	public void getNearObjects(ArrayList<GameObject> objects, GameObject g) {
		objects.addAll(largeObjects);
		root.getNearObjects(objects, g);
	}
	
	public double getMinX() {
		return min[0];
	}
	
	public double getMinY() {
		return min[1];
	}
	
	public double getMinZ() {
		return min[2];
	}
	
	public double getMinSize() {
		return minSize;
	}
	
	public ArrayList<DynamicObject> getDynamicObjects() {
		return dynamicObjects;
	}
}